package malohlava.apps.mendeley
{
  import malohlava.apps.mendeley._
  import malohlava.apps.mendeley.scribe.builder.api._;
  
  import org.scribe.oauth._
  import org.scribe.builder._
  import org.scribe.model._

  import scala.io._
  import java.io._
  import scala.collection.immutable._

  trait Command {
    def getUrl: String 

    def getMethod: String

    def isAccessTokenRequired: Boolean

    def getRequiredParameters: List[String]

    def getOptionalParameters: List[String]
  }

  trait ParameterizedCommand {
    def setParameters(parameters: HashMap[String, String])
  }
    
  abstract class AbstractCommand(
    name: String,
    url: String,
    requiredParameters: List[String],
    optionalParameters: List[String],
    method: String, /* get, post, delete */
    accessTokenRequired: Boolean
    ) extends Command {
    
    def this(name:String, url:String, requiredParameters: List[String]) = 
      this(name, url, requiredParameters, Nil, "get", false) 

    override def getUrl: String = url
    
    override def getMethod: String = method

    override def isAccessTokenRequired: Boolean = accessTokenRequired

    override def getRequiredParameters: List[String] = requiredParameters

    override def getOptionalParameters: List[String] = optionalParameters

  }

  abstract class AbstractParameterizedCommand(
    parameters: HashMap[String,String],
    name: String,
    url: String,
    requiredParameters: List[String],
    optionalParameters: List[String],
    method: String, /* get, post, delete */
    accessTokenRequired: Boolean
    ) extends AbstractCommand(name, url, requiredParameters, optionalParameters, method, accessTokenRequired)
    with ParameterizedCommand {

      setParameters(parameters)

      var resolvedUrl:String = url

      def this(parameters:HashMap[String,String], name:String, url:String, requiredParameters: List[String]) = 
        this(parameters, name, url, requiredParameters, Nil, "get", false) 

      override def setParameters(parameters: HashMap[String, String]) = {
        if (parameters != null)
          resolvedUrl = mapUrl(url, parameters)
        else
          resolvedUrl = null
      }

      override def getUrl: String = if (resolvedUrl != null)
          resolvedUrl
        else
          url

      protected def mapUrl(url: String, parameters: HashMap[String, String]):String = {
      var fullUrl = url

      // replace required parameters
      getRequiredParameters.foreach(
        param => {
          if (parameters.contains(param))
           fullUrl = fullUrl.replace(param, parameters(param))
          else
            throw new RequiredParameterNotFound(param)
        }
      )
      // replace optional parameters
      getOptionalParameters.foreach(
        param => 
          if (parameters.contains(param))
           fullUrl = fullUrl.replace(param, parameters(param))
      )

      fullUrl
    }

  }

  case class DocumentDetailsCmd(parameters: HashMap[String, String]) extends AbstractParameterizedCommand(parameters, "documentDetails", "/oapi/library/documents/%(id)s/", List("%(id)")) 

  case class Tagged(parameters: HashMap[String, String]) extends AbstractParameterizedCommand(parameters, "tagged", "/oapi/documents/tagged/%(tag)s/", List("%(tag)")) 
  
 case class LibraryTagStats() extends AbstractParameterizedCommand(null, "libraryTagStats", "/oapi/library/tags/", Nil, Nil, "get", true) 
 
 case class Library() extends AbstractParameterizedCommand(null, "library", "/oapi/library/", Nil, Nil, "get", true) 
 
 case class Collections() extends AbstractParameterizedCommand(null, "collections", "/oapi/library/collections/", Nil, Nil, "get", true) 
  
  class MendeleyClient(apiKey: String, apiSecret: String) {

    val MENDELEY_URL:String = "http://www.mendeley.com"
    val DEFAULT_TOKEN_FILE:String = ".keys"
    val AUTHORIZATION_URL:String = "http://www.mendeley.com/oauth/authorize?oauth_token="

    private val oauthService = new ServiceBuilder()
          .provider(classOf[MendeleyApi])
          .apiKey(apiKey)
          .apiSecret(apiSecret)
          .build()

    private var requestToken:Token = null
    private var accessToken:Token = null

    def authorize = {

      obtainTokens match {
        case (null, null) => throw new AccessDeniedException("Cannot obtain authorization tokens!") 
        case (r, a) => {
            requestToken = r
            accessToken = a
          }
      }
    }

    def printAuthorizationInfo = {
      println("Request token: " + requestToken)
      println("Access token: " + accessToken)
    }

    private def obtainTokens : (Token, Token) = {

      try {
        loadTokens match { case (reqT, accT) => (reqT, accT) }
      } catch {
          case e: TokensNotFoundException => {
            requestTokens 
              match { 
                  case (reqT, accT) => { 
                      saveTokens(reqT, accT)
                      (reqT, accT)
                    }
              }
          }
      }
    }

    protected def requestTokens : (Token, Token) = {
      val requestToken = oauthService.getRequestToken()

      println("Goto to: " + AUTHORIZATION_URL + requestToken.getToken); 
      println("and type the verifier");
      print(">");

      val verifierCode = readLine

      val verifier = new Verifier(verifierCode)
      val accessToken = oauthService.getAccessToken(requestToken, verifier)
            
      (requestToken, accessToken)
    }

    protected def loadTokens : (Token, Token) = {
      try {
        val lines = Source.fromFile(DEFAULT_TOKEN_FILE).getLines.toList

        (new Token(lines(0), lines(1)), new Token(lines(2), lines(3)))            
      } catch {
          case e: Exception => throw new TokensNotFoundException(e)
      }
    }

    protected def saveTokens(requestToken:Token, accessToken:Token) = {
      val fw = new FileWriter(DEFAULT_TOKEN_FILE)

      try {
        fw.write(requestToken.getToken());
        fw.write('\n');
        fw.write(requestToken.getSecret());
        fw.write('\n');
        fw.write(accessToken.getToken());
        fw.write('\n');
        fw.write(accessToken.getSecret());
        fw.write('\n');
      } finally {
        fw.close()
      }
    }

    def executeCommand(cmd: AbstractCommand) : String = {
      val method = cmd.getMethod match {
        case "get" => Verb.GET
        case "post" => Verb.POST
        case "delete" => Verb.DELETE
        case _ => Verb.GET
      }
      val url = MENDELEY_URL + cmd.getUrl
      val request = new OAuthRequest(method, url);

      // sign the request
      if (cmd.isAccessTokenRequired) {
        oauthService.signRequest(accessToken, request)
      }

      // send request
      val response = request.send();

      // return response body
      response.getBody()
    }
  }

  object MendeleyRWGClient extends MendeleyClient("d45a9a5b016a409b46eacb999376eb1c04d17c210", "6815b627d7c1a6f164173d7cc336a949") {
  }
}
